/**
 * 
 */
package com.peak.spring.boot.builder.repository;

import java.util.Collection;
import java.util.Date;
import java.util.List;

import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Join;
import javax.persistence.criteria.JoinType;
import javax.persistence.criteria.Path;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;

import org.apache.commons.lang3.StringUtils;
import org.hibernate.jpa.criteria.path.PluralAttributePath;
import org.springframework.data.jpa.domain.Specification;

import com.google.common.collect.Lists;
import com.peak.spring.boot.api.repository.FilterParam;
import com.peak.spring.boot.api.repository.FilterParam.Operator;
import com.peak.spring.boot.tools.Converts;
import com.peak.spring.boot.tools.Emptys;

/**
 * @author Frank
 * @param <T>
 *
 */
public class DefaultSpecification<T> implements Specification<T> {

	private Collection<FilterParam> filterParams;

	public DefaultSpecification(Collection<FilterParam> filterParams) {
		this.filterParams = filterParams;
	}

	@SuppressWarnings({ "rawtypes" })
	@Override
	public Predicate toPredicate(Root<T> root, CriteriaQuery<?> query, CriteriaBuilder builder) {
		if (Emptys.isNotEmpty(filterParams)) {

			List<Predicate> predicates = Lists.newArrayList();
			Predicate predicate = null;
			for (FilterParam filter : filterParams) {
				if (Operator.DISTINCT.equals(filter.getOperator())) {
					query.distinct(true);
					continue;
				}

				// 计算访问路径
				Path expression = calculatePath(filter, root, query);

				// 获取查询条件
				predicate = predicateFactory(builder, filter, expression);

				if (Emptys.isNotEmpty(predicate)) {
					predicates.add(predicate);
				}
			}

			// 将所有条件用 and 联合起来
			if (!predicates.isEmpty()) {
				return builder.and(predicates.toArray(new Predicate[predicates.size()]));
			}
		}

		return builder.conjunction();
	}

	@SuppressWarnings({ "rawtypes", "unchecked" })
	protected Predicate predicateFactory(CriteriaBuilder builder, FilterParam filter, Path expression) {
		Predicate predicate = null;
		
		// 如有需要进行一些值的转换
		Object value = convertValueIf(filter, expression);

		Operator operator = filter.getOperator();
		switch (operator) {
		case EQ:
			predicate = builder.equal(expression, value);
			break;
		case NEQ:
			predicate = builder.notEqual(expression, value);
			break;
		case LLIKE:
			predicate = builder.like(expression, "%" + value);
			break;
		case RLIKE:
			predicate = builder.like(expression, value + "%");
			break;
		case LIKE:
			predicate = builder.like(expression, "%" + value + "%");
			break;
		case GT:
			predicate = builder.greaterThan(expression, (Comparable) value);
			break;
		case LT:
			predicate = builder.lessThan(expression, (Comparable) value);
			break;
		case GTE:
			predicate = builder.greaterThanOrEqualTo(expression, (Comparable) value);
			break;
		case LTE:
			predicate = builder.lessThanOrEqualTo(expression, (Comparable) value);
			break;
		case IN:
			predicate = expression.in(((Object[]) value));
			break;
		case NOTIN:
			predicate = builder.not(expression.in(((Object[]) value)));
			break;
		case ISNULL:
			predicate = builder.isNull(expression);
			break;
		case NOTNULL:
			predicate = builder.isNotNull(expression);
			break;
		case EMPTY:
			predicate = builder.or(builder.isNull(expression), builder.equal(expression, ""));
			break;
		case NOTEMPTY:
			predicate = builder.and(builder.isNotNull(expression), builder.notEqual(expression, ""));
			break;
		default:
			break;
		}
		return predicate;
	}

	protected Path<?> calculatePath(FilterParam filter, Root<T> root, CriteriaQuery<?> query) {
		Path<?> path = null;
		// nested path translate, 如Task的名为"user.name"的filedName,
		// 转换为Task.user.name属性
		String[] names = StringUtils.split(filter.getFieldName(), ".");
		if (names.length > 1 && sniff(names, root)) {
			query.distinct(true);
			Join<?, ?> join = root.join(names[0], JoinType.LEFT);
			for (int i = 1; i < names.length - 1; i++) {
				join = join.join(names[i], JoinType.LEFT);
			}
			path = join.get(names[names.length - 1]);
		} else {
			path = root;
			for (int i = 0; i < names.length; i++) {
				path = path.get(names[i]);
			}
		}
		return path;
	}

	// 如果属性是集合，需使用join
	protected boolean sniff(String[] names, Root<T> root) {
		boolean needJoin = false;
		Path<?> path = root;
		for (int i = 0; i < names.length; i++) {
			path = path.get(names[i]);
			if (PluralAttributePath.class.isAssignableFrom(path.getClass())) {
				needJoin = true;
				break;
			}
		}
		return needJoin;
	}

	// 对一些特殊值做处理
	@SuppressWarnings({ "unchecked", "rawtypes" })
	protected Object convertValueIf(FilterParam filter, Path<?> expression) {
		Object value = filter.getValue();
		Operator operator = filter.getOperator();
		Class valueType = expression.getModel().getBindableJavaType();
		if (!Operator.IN.equals(operator) && !Operator.NOTIN.equals(operator)) {
			if (Date.class.isAssignableFrom(valueType)) {
				value = Converts.toDate(value);
			} else if (Boolean.class.isAssignableFrom(valueType) && !(value instanceof Boolean)) {
				value = Converts.toBoolean(value);
			} else if (Enum.class.isAssignableFrom(valueType) && !(value instanceof Enum)) {
				value = Enum.valueOf(valueType, Converts.toString(value));
			}
		} else {
			String[] values = StringUtils.split(Converts.toString(value), ",");
			value = Converts.toArrayByClass(values, valueType);
		}
		return value;
	}
}
